#include "Wilson.h"

#include <cassert>

struct StringCompareFunctor
{
	bool operator()(const char* str, const eastl::string& str2) { return str < str2; }
	bool operator()(const eastl::string& str, const char* str2) { return str < str2; }
};

eastl::string AddSlashes(const eastl::string& s, const char* beforechars = "\\\"'")
{
	eastl::string str = s;
	size_t start_pos = 0;
	char repl[3] = "\\0";
	while((start_pos = str.find_first_of(beforechars, start_pos)) != eastl::string::npos) {
		repl[1] = str[start_pos];
		str.replace(start_pos, 1, repl);
		start_pos += 2;
	}
	return str;
}

bool DataNode::HasElement(const char* key) const
{
	if (Type != DICT) return false;

	DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
	return i != Dict.end();
}

bool DataNode::ElementHasStringValue(const char* key, const char* val) const
{
	if (Type != DICT) return false;

	DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
	if (i != Dict.end())
		return i->second->Type == STRING && i->second->StringVal == val;
	return false;
}

const DataNode* DataNode::GetElement(const char* key) const
{
	if (Type != DICT) return 0;

	DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
	if (i != Dict.end())
		return i->second;
	return 0;
}

const DataNode* DataNode::GetElementSafe(const char* key) const
{
	static DataNode empty;
	if (Type != DICT) return &empty;

	DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
	if (i != Dict.end())
		return i->second;
	return &empty;
}

double DataNode::GetElementAsNumber(const char* key, double default) const
{
	if (Type != DICT) return default;

	DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
	if (i != Dict.end())
	{
		if (i->second && i->second->Type == NUMBER)
			return i->second->Number;
	}
	return default;
}

eastl::string DataNode::GetElementAsString(const char* key, const char* default) const
{
	if (Type != DICT) return default;

	DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
	if (i != Dict.end())
	{
		if (i->second && i->second->Type == STRING)
			return i->second->StringVal;
	}
	return default;
}

void DataNode::Destroy()
{
	for (ListType::iterator i=List.begin(); i!=List.end(); ++i)
	{
		delete *i;
	}

	for (DictType::iterator i=Dict.begin(); i!=Dict.end(); ++i)
	{
		delete i->second;
	}

	Dict.clear();
	StringVal.clear();
	List.clear();
	Number = 0;
	Type = NONE;
}

eastl::string DataNode::ValueAsString(const char* default) const
{
	if (Type == STRING)
		return StringVal;
	if (Type == NUMBER)
	{
		char temp[256];
		sprintf_s(temp, "%g", Number);
		return temp;
	}
	return default;
}

double DataNode::ValueAsNumber(double default) const
{
	if (Type == STRING)
	{
		return atof(StringVal.c_str());
	}
	if (Type == NUMBER)
		return Number;
	return default;
}

bool DataNode::IsString( const char* str ) const
{
	return Type == STRING && StringVal == str;
}


DataNode& DataNode::operator[]( const char* str )
{
	if (Type != DICT)
		Destroy();

	Type = DICT;

	DictType::iterator it = Dict.find_as(str, StringCompareFunctor());
	if (it != Dict.end())
	{
		return *(it->second);
	} else
	{
		DataNode* n = new DataNode;
		Dict[str] = n;
		return *n;
	}
}

DataNode& DataNode::operator=( const DataNode& n )
{
	if (&n == this)
		return *this;
		
	Destroy();
	Type = n.Type;
	switch (Type)
	{
	case STRING:
		StringVal = n.StringVal;
		break;
	case NUMBER:
		Number = n.Number;
		break;
	case LIST:
		List = n.List;
		break;
	case DICT:
		Dict = n.Dict;
		break;
	}
	return *this;
}

const char* DataNode::Parse( const char* str )
{
	str = StripWhitespace(str);
	for (;;)
	{
		char t = *str;
		switch (t)
		{
		case '"':
		case '\'':
			Type = STRING;
			return ParseString(str);
		case '[':
			Type = LIST;
			return ParseList(str+1);
		case '{':
			this->Type = DICT;
			return ParseDict(str+1);
		case '#':
			Type = NONE;
			return str+1;
		case 0:
			Type = NONE;
			return str;
		case ';':
		case ',':
			str++;
			continue;
		default:
			if (isalpha(t))
				return ParseWordString(str);
			else if (isdigit(t) || t == '-')
				return ParseNumber(str);
			else
			{
				Type = NONE;
				return 0;
			}
		}
	}
}

const char* DataNode::StripWhitespace( const char* str )
{
	while (*str && isspace(*str))
		str++;
	return str;
}

const char* DataNode::ParseNumber( const char* str )
{
	Type = NUMBER;
	char* finish = 0;
	Number = strtod(str, &finish);
	return finish;
}

const char* DataNode::ParseString( const char* str )
{
	const char* first = str;
	str++;
	char term[2] = " ";
	*term = *first;
									 
	const char* cur = str;
	size_t terminator_at = 0;
	do
	{
		terminator_at = strcspn(cur, term);
		cur = cur+terminator_at+1;
	} while (*(cur-2) == '\\');

	assert(*(str+terminator_at) == *first);
	StringVal.assign(str, str+terminator_at);
	return str+terminator_at+1;
}

const char* DataNode::ParseWordString( const char* str )
{
	const char* first = str;
	str++;
	while (*str && isalnum(*str))
		str++;
	if (!strncmp(first, "true", str-first))
	{
		Type = NUMBER;
		Number = 1.0;
	} else if (!strncmp(first, "false", str-first))
	{
		Type = NUMBER;
		Number = 0.0;
	} else if (!strncmp(first, "null", str-first) && !strncmp(first, "none", str-first))
	{
		Type = NONE;
	} else
	{
		Type = STRING;
		StringVal.assign(first, str);
	}
	return str;
}

const char* DataNode::ParseList( const char* str )
{
	str = StripWhitespace(str);
	while (*str && *str != ']')
	{
		str = StripWhitespace(str);
		DataNode* node = new DataNode;
		str = node->Parse(str);
		List.push_back(node);

		str = StripWhitespace(str);
		if (*str == ',' || *str == ';')
			str = StripWhitespace(str+1);
	}

	if (*str)
		return str+1;

	return str;
}

const char* DataNode::ParseDict( const char* str )
{
	str = StripWhitespace(str);
	while (*str && *str != '}')
	{
		str = StripWhitespace(str);
		
		const char* first = str;
		eastl::string key;
		
		if (*first == '"' || *first == '\'')
		{
			str++;

			/// quoted string
			while (*str && *str != *first)
				str++;
			if (!*str)
				return str;

			key.assign(first+1, str);

			str++; /// skip terminator
		}
		else if (isalpha(*first))
		{
			/// single-word string
			while (*str && isalnum(*str))
				str++;
			if (!*str)
				return str;

			key.assign(first, str);
		}
		else
		{
			fprintf(stderr, "While reading Wilson dictionary: Dictionary key not a string (starts with %c)", *first);
			return str;
		}

		str = StripWhitespace(str);
		if (*str == ':' || *str == '=')
			str++;

		DataNode* node = new DataNode;
		str = node->Parse(str);
		Dict[key] = node;

		str = StripWhitespace(str);
		if (*str == ',' || *str == ';')
			str = StripWhitespace(str+1);
	}

	if (*str)
		return str+1;
	return str;
}

eastl::string DataNode::Encode() const
{
	switch (Type)
	{
	case NONE:
		return "#";
	case NUMBER:
		{
			char buf[32];
			sprintf(buf, "%g", this->Number);
			return buf;
		}
	case STRING:
		return eastl::string("\"") + AddSlashes(this->StringVal) + "\"";
	case LIST:
		{
			eastl::string str;
			str = "[ ";
			for (ListType::const_iterator i=List.begin(); i!=List.end(); i++)
			{
				DataNode* node = (DataNode*)(*i);
				str += node->Encode();
				str += ", \n";
			}
			str += "]\n";
			return str;
		}
	case DICT:
		{
			eastl::string str;
			str = "{\n";
			for (DictType::const_iterator i=Dict.begin(); i!=Dict.end(); i++)
			{
				DataNode* node = (DataNode*)(i->second);
				str += eastl::string("\"") + AddSlashes(i->first) + "\" = ";
				str += node->Encode();
				str += ", \n";
			}
			str += "}\n";
			return str;
		}
	}

	return "";
}
